package utilities.clock;

import java.util.ArrayList;

import program.Program;

import controller.algorithms.AlgorithmManager;
import controller.environment.ground.Ground;
import controller.taskManagement.Task;

import reference.codes.CodeTime;
import reference.codes.CodeError;
import reference.codes.CodeView;
import reference.constants.ConstantProblem;

/**
 * Clock of the game
 * @author Lcompere
 *
 */
public class Clock implements Runnable {
	
	private static Clock m_clock = null;
	private Thread m_runner = null;
	private ArrayList<ClockListener> m_listeners;
	private int m_counter;
	
	/**
	 * Constructor of the class
	 * PRIVATE: the class is a singleton
	 */
	private Clock(){
		System.out.println("Clock:: creation of the clock");
		m_listeners = new ArrayList<ClockListener>();
		m_counter = 0;
	}
	
	/**
	 * Returns the current clock or create it if it does not exists 
	 * @return the current Clock
	 */
	public static Clock getClock(){
		if(m_clock==null)
			m_clock = new Clock();
		return m_clock;
	}
	
	/**
	 * Functions called when the clock starts that will run during 
	 * all the program and frequently call the function acts() of the different listeners
	 */
	public void run() {
		Thread thisThread = Thread.currentThread();
		while(m_runner==thisThread){
			m_counter++;
			if(m_counter%ConstantProblem.STEP_TO_DISPLAY_INFO==0){
				System.out.println("----------------");
				System.out.println("Step " + m_counter);
				AlgorithmManager.getInstance().getAlgorithm().displayInfo();
				System.out.println("----------------");
			}
			if(m_counter==ConstantProblem.MAX_STEP_ITERATION){
				System.out.println("----------------------");
				System.out.println("--- END OF PROGRAM ---");
				System.exit(0);
			}
			try {
				switch(Program.TYPE_OF_VIEW){
					case CodeView.SIMPLE_NORMAL_VIEW:
						Thread.sleep(CodeTime.CLOCK_TIME_TO_WAIT);
						break;
					default:
						break;
				}
				fireClockEvent();
			} catch (InterruptedException e) {
				System.out.println("Clock::ERROR : The thread of the clock has generated an error");
				System.out.println(e.getMessage());
				System.exit(CodeError.PROBLEM_WITH_CLOCK);
			}
		}
	}
	
	/**
	 * Function that calls the function acts() of all the listeners
	 */
	@SuppressWarnings("unchecked")
	public void fireClockEvent(){
		for(ClockListener listener : (ArrayList<ClockListener>)m_listeners.clone())
			listener.acts();
	}
	
	/**
	 * Starts the clock
	 */
	public void start(){
		if(m_runner==null){
			m_runner = new Thread(this);
			m_runner.start();
		}
	}
	
	/**
	 * Stops the clock
	 */
	public void stop(){
		if(m_runner!=null){
			boolean attempt = true;
			while(attempt && m_runner!=null){
				try{
					m_runner.checkAccess();
					attempt = false;
					m_runner.interrupt();
					m_runner=null;	
				}catch(SecurityException e){
					
				}
			}
		}
	}
	
	/**
	 * Adds the clockListener in a certain order :
	 * <br/>1. Ground
	 * <br/>2. Task
	 * <br/>3. Entity
	 * @param new_listener the new listener to add
	 */
	public void addClockListener(ClockListener new_listener){
		new_listener.setClock(this);
		// If Ground, put it at the first place
		if(new_listener instanceof Ground){
			m_listeners.add(0, new_listener);
		}else if(new_listener instanceof Task){
			// If Task, put it right after the Ground
			int position = 0;
			for(int i=0;i<m_listeners.size();i++){
				if(m_listeners.get(i) instanceof Ground)
					position = i;
			}
			if(position==0)
				m_listeners.add(0, new_listener);
			else if(position+1 >= m_listeners.size())
				m_listeners.add(new_listener);
			else
				m_listeners.add(new_listener);
		}else{
			// Other are put at the end
			m_listeners.add(new_listener);
		}
	}
	
	public void removeClockListener(ClockListener old_listener){
		m_listeners.remove(old_listener);
	}

}
